/*///////////////////////////////////////////////////////////
*
* FILE:		client.c
* AUTHOR:	Tuan Dang and Paul Spangler
* PROJECT:	CS 3251 Project 2 - Professor Traynor
* DESCRIPTION:	Network Client Code
*
*////////////////////////////////////////////////////////////

/* Included libraries */

#include <stdio.h>        /* for printf() and fprintf() */
#include <sys/socket.h>	  /* for socket(), connect(), send(), and recv() */
#include <arpa/inet.h>	  /* for sockaddr_in and inet_addr() */
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <netdb.h>
#include <ctype.h>
#include "common.h"

/* Constants */
#define IPADDR "127.0.0.1"      /* The ip address of the server */
#define PORT 28134              /* The port of the server */
#define MAX_NAME 255

/**
 * Method to empty the input buffer
 */
void empty_inputbuf() {
	char temp;
	while((temp = getchar()) != '\n' && temp != EOF);
}

/**
 * Method to remove newline characters from inputed strings and
 * close them with a NULL character
 */
void remove_newline(char *str) {
	char *newline = strchr(str, '\n');
	if(newline != NULL)
		*newline = '\0';
}

/**
 * Method that asks for userID from the user
 */
void input_user(char *userID) {
	fprintf(stdout, "\nUserID: ");
	fflush(stdout);
	//scanf("%s", userID);
	fgets(userID, MAX_NAME, stdin);
	remove_newline(userID);
	//empty_inputbuf();
}

/**
 * Method that asks for coordinates from the user
 */
void input_cord(Coordinate *cord) {
	int check = 1;
	while(check) {
		fprintf(stdout, "\nLattitude: ");
		fflush(stdout);
		check = !scanf("%f", &(cord->lattitude));
		empty_inputbuf();
		if(check == 1)
			fprintf(stdout, "\nInvalid input.\n");
	}

	check = 1;
	while(check) {
		fprintf(stdout, "\nLongitude: ");
		fflush(stdout);
		check = !scanf("%f", &(cord->longitude));
		empty_inputbuf();
		if(check == 1)
			fprintf(stdout, "\nInvalid input.\n");
	}
}

/**
 * Method that initiates TCP connections with the server.
 * Attempts to translate host names into IP addresses
 * Return:
 *   an integer identifier of the socket
 *   -1 if failed creating/connecting socket
 */
static int startConnections(char *server, char *port) {
	int sock = -1;
	
	if(isdigit(server[0])) {
		struct sockaddr_in servAddr;

		sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sock < 0) {
			perror("Failed to open socket");
			exit(EXIT_FAILURE);
		}

		memset(&servAddr, 0, sizeof(servAddr));
		servAddr.sin_family = AF_INET;
		if (inet_pton(AF_INET, server, &servAddr.sin_addr.s_addr) <= 0) {
			perror("String to server address failed. Possible invalid address string.");
			exit(EXIT_FAILURE);
		}
		servAddr.sin_port = htons(atoi(port));

		if (connect(sock, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0) {
			perror("Failed to connect to the server");
			exit(EXIT_FAILURE);
		}
	} else {
		struct addrinfo addrCriteria;
		struct addrinfo *servAddr;
		struct addrinfo *addr;
		int val;

		memset(&addrCriteria, 0, sizeof(addrCriteria));
		addrCriteria.ai_family = AF_INET;
		addrCriteria.ai_socktype = SOCK_STREAM;
		addrCriteria.ai_protocol = IPPROTO_TCP;

		val = getaddrinfo(server, port, &addrCriteria, &servAddr);
		if(val != 0) {
			perror("Failed to resolve host names/addresses.");
			perror(gai_strerror(val));
			exit(EXIT_FAILURE);
		}

		for(addr = servAddr; addr != NULL; addr = addr->ai_next) {
			sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);

			if(sock < 0)
				continue;

			if(connect(sock, addr->ai_addr, addr->ai_addrlen) == 0)
				break;

			close(sock);
			sock = -1;
		}
		
		freeaddrinfo(servAddr);
	}
	
	return sock;
}

/**
 * Method that handles how the client sends update information
 * to the server. Asks for user inputs to update the server
 * with.
 * Returns:
 *   0 - if there is an error
 *   1 - success in sending updates
 */
static int update(ReadData *rd, FILE *stream) {
	char userID[MAX_NAME];
	Coordinate *cord = malloc(sizeof(Coordinate));
	char response, temp;

	if(cord == NULL) {
		perror("Cannot create a coordinate object.");
		return 0;
	}

	fprintf(stdout,
			"############## Update ##############\n"
			"Type in answer for prompts and press\n"
			"the <Enter> key.\n");
	
	input_user(userID);
	input_cord(cord);
	
	fputc('0', stream);
	fwrite(cord, sizeof(Coordinate), 1, stream);
	fprintf(stream, "%s\n", userID);
	fflush(stream);

	free(cord);

	system("clear");
	if (!read_byte(rd, &response)) {
		perror("Error attempting to read server response.");
		return 0;
	}

	/* flush the file stream */
	if(!read_byte(rd, &temp)) {
		perror("Error attempting to read server response.");
		return 0;
	}

	if (response == 0) {
		perror("Server was unable to update data. Bad userID");
		return 1;
	} else if (response == 1)
		fprintf(stdout, "\nServer successfully received update\n");

	return 1;
}

/**
 * Method that handles asks for a list of userIDs to
 * send to the server to get coordinate data for each
 * userID.
 * Return:
 *   0 - if there is an error
 *   1 - success
 */
static int friends(ReadData *rd, FILE *stream) {
	char name[MAX_NAME];
	int count = 0;
	char status = '\0';
	Coordinate *cord = malloc(sizeof(Coordinate));
	int length;

	if(cord == NULL) {
		perror("Cannot create a coordinate object.");
		return 0;
	}

	fprintf(stdout,
			"############## Friends ##############\n"
			"Type in user IDs and press the\n"
			"<Enter> key. When done, type \"done\"\n"
			"without the quotes and press <Enter>.");

	while(1) {
		input_user(name);
		if(strcmp("done", name) == 0)
			break;
		else {
			if(count == 0)
				fputc('1', stream);
			fprintf(stream, "%s\t", name);
			count++;
		}
	}

	system("clear");

	if(count == 0) {
		fprintf(stdout, "No users inputed\n");
		return 1;
	} else {
		fprintf(stream, "\n");
		fflush(stream);
	}
	
	while(1) {
		if(!read_byte(rd, &status)) {
			perror("Error attempting to read server response.");
			return 0;
		}

		if(status != '\n') {
			if(!read_data(rd, cord, sizeof(Coordinate))) {
				perror("Error attempting to read server response.");
				free(cord);
				return 0;
			}

			length = read_string(rd, name, MAX_NAME + 1);
			if(length < 0) {
				perror("Error attempting to read server response.");
				free(cord);
				return 0;
			}

			if(length > 0) {
				name[length] = '\0';
				if(status == 0)
					fprintf(stdout, "\n%s: Not Found\n", name);
				else
					fprintf(stdout, "\n%s: Found\n", name);
				fprintf(stdout, "%f %f\n", cord->lattitude, cord->longitude);
			}
		} else
			break;
	}

	fprintf(stdout, "\nPress <Enter> to continue.\n");
	getchar();
	system("clear");
	free(cord);
	return 1;
}

/**
 * Method that retrieves the coordinate history from
 * the server for the inputed userID.
 * Return:
 *   0 - error
 *   1 - success
 */
static int history(ReadData *rd, FILE *stream) {
	char userID[MAX_NAME];
	char temp;
	unsigned int count;
	int i;
	Coordinate *cord = malloc(sizeof(Coordinate));

	if(cord == NULL) {
		perror("Cannot create a coordinate object.");
		return 0;
	}

	fprintf(stdout,
			"############## History ##############\n"
			"Type in your user ID and press the\n"
			"<Enter> key.\n");

	input_user(userID);

	fprintf(stream, "2%s\n", userID);
	fflush(stream);

	if(!read_data(rd, &count, sizeof(count))) {
		perror("Error attempting to read server response.");
		return 0;
	}

	fprintf(stdout, "\n");

	if(count == 0)
		fprintf(stdout, "User does not exist, or there are no records.\n");
	else {
		for(i = 0; i < count; i++) {
			if(!read_data(rd, cord, sizeof(Coordinate))) {
				perror("Error attempting to read server response.");
				free(cord);
				return 0;
			}

			fprintf(stdout, "%f %f\n", cord->lattitude, cord->longitude);
		}
	}

	/* flush the file stream */
	if(!read_byte(rd, &temp)) {
		perror("Error attempting to read server response.");
		return 0;
	}

	fprintf(stdout, "\nPress <Enter> to continue.\n");
	getchar();
	system("clear");
	free(cord);
	return 1;
}

/* The main function */
int main(int argc, char *argv[]) {
	int status = 1;
	char indicator;
	
	if(argc != 3) {
		fprintf(stderr, "Usage: %s <port> <server name/ip address>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	int sock = startConnections(argv[2], argv[1]);

	if(sock < 0) {
		perror("Unable to resolve host name. Socket creation/connection failed.");
		exit(EXIT_FAILURE);
	}
	
	FILE *stream = fdopen(sock, "w");
	ReadData rd = read_init(sock);

	system("clear");
	
	while(status) {
		fprintf(stdout,
			"\n############## Main Screen ##############\n"
			"0) Update position\n"
			"1) Friend locations\n"
			"2) History\n"
			"3) Quit\n"
			"\nSelect an option and press <Enter>: ");
		fflush(stdout);

		indicator = getchar();
		empty_inputbuf();
		system("clear");
		
		switch(indicator) {
			case MESSAGE_UPDATE:
				status = update(&rd, stream);
				break;
			case MESSAGE_FRIENDS:
				status = friends(&rd, stream);
				break;
			case MESSAGE_HISTORY:
				status = history(&rd, stream);
				break;
			case MESSAGE_LEAVE:
				fprintf(stream, "3\n");
				fflush(stream);
				status = 0;
				break;
			default:
				fprintf(stderr, "\nPlease select a number from 0 to 3.\n"
						"Press <Enter> to continue.\n");
				getchar();
				empty_inputbuf();
		}
	}

	fprintf(stdout, "Closing the client\n");
	fclose(stream);
	close(sock);
	return 0;
}
